﻿using System;
using System.Collections.Generic;
using System.Linq;
using TribesPSO;


namespace GaussianBenchmark
{
    internal class GaussianFitFunction : IObjectiveFunction
    {
        int evaluations = 0;

        public int Evaluations
        {
            get { return evaluations; }
        }

        private List<Tuple<double, double>> data;
        private int dimensions;

        EuclidianVector maxBounds;
        EuclidianVector minBounds;

        public EuclidianVector MaxBounds
        {
            get
            {
                return maxBounds;
            }
        }

        public EuclidianVector MinBounds
        {
            get
            {
                return minBounds;
            }
        }

        public EuclidianVector InitialGuess
        {
            get { return null; }
        }

        public GaussianFitFunction(int NumberOfCurves, IEnumerable<Tuple<double, double>> Data)
        {
            data = new List<Tuple<double, double>>(Data);
            dimensions = 3 * NumberOfCurves;

            maxBounds = ComputeMinBounds(dimensions, data);
            minBounds = ComputeMaxBounds(dimensions, data);

            EuclidianVector Range = maxBounds - minBounds;
        }

        #region IObjectiveFunction members
        public int Dimensions
        {
            get { return dimensions; }
        }

        /// <summary>
        /// Evaluates the performance of a point at the normalized Euclidian Vector
        /// </summary>
        /// <param name="Position">A normalized position representing a solution to the problem</param>
        /// <returns></returns>
        public double Evaluate(EuclidianVector Position)
        {
            evaluations += 1;
            MultiGaussianCurve CandidateFunction = GetSolution(Position);
            double Agrigator = 0;
            foreach (var d in data)
            {
                double Delta = CandidateFunction.Evaluate(d.Item1) - d.Item2;
                Agrigator += Delta * Delta;
            }
            return Agrigator;
        }
        #endregion

        /// <summary>
        /// Decomposes the solution at the normalized Position p into a collection of gaussian functions that have been
        /// de-normalized to match the original data
        /// </summary>
        /// <param name="Position">A normalized position representing a solution to the problem</param>
        /// <returns></returns>
        public List<ImmutableGaussianCurve> DecomposeSolution(EuclidianVector Position)
        {
            //Divide each dimension by the normalization constant for that dimension to get the denormalized dimensions
            List<ImmutableGaussianCurve> Components = new List<ImmutableGaussianCurve>();
            for (int n = 0; n < Position.Dimensions / 3; n++)
            {
                int baseIndex = n * 3;
                Components.Add(
                    new ImmutableGaussianCurve(
                        Position[baseIndex],
                        Position[baseIndex + 1],
                        Position[baseIndex + 2]
                        )
                    );
            }
            return Components;
        }

        /// <summary>
        /// Decomposes the solution at a normalized position p into a MultiGaussianCurve.  The position P will be
        /// de-normalized so the resulting MultiGaussianCurve will have the same dimensions as the data passed into
        /// the constructer
        /// </summary>
        /// <param name="Position">A normalized position representing a solution to the problem</param>
        /// <returns></returns>
        public MultiGaussianCurve GetSolution(EuclidianVector Position)
        {
            //Construct the multi gaussian function
            List<ImmutableGaussianCurve> Components = DecomposeSolution(Position);//Decompose Solution will handle de-normilization
            return new MultiGaussianCurve(Components);
        }

        private static EuclidianVector ComputeMinBounds(int dimensions, List<Tuple<double, double>> data)
        {
            List<double> mins = new List<double>();
            for (int n = 0; n < dimensions / 3; n++)
            {
                mins.Add(data.Min(o => o.Item1));
                mins.Add(0);
                mins.Add(0);
            }
            return new EuclidianVector(mins);
        }

        private static EuclidianVector ComputeMaxBounds(int dimensions, List<Tuple<double, double>> data)
        {
            double range = data.Max(o => o.Item1) - data.Min(o => o.Item1);
            double increment = range / data.Count();
            double volume = data.Sum(o => o.Item2 * increment);
            List<double> maxes = new List<double>();
            for (int n = 0; n < dimensions / 3; n++)
            {
                maxes.Add(data.Max(o => o.Item1));
                maxes.Add((range / 6) * (range / 6));
                maxes.Add(volume);
            }
            return new EuclidianVector(maxes);
        }
    }
}
